package com.zhangsun.springShiro.framework.jwt;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.JSON;
import com.zhangsun.springShiro.base.bean.ResponseData;
import com.zhangsun.springShiro.common.utils.ResponseUtil;
import com.zhangsun.springShiro.system.bean.TheUser;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.List;

/**
 * jwt过滤器
 */
public class JwtFilter extends BasicHttpAuthenticationFilter {

    /**
     * token参数名
     */
    @Value("${jwt.header}")
    private String header;

    /**
     * token前缀
     */
    @Value("${jwt.tokenType}")
    private String tokenType;

    /**
     * 忽略的地址
     */
    @Value("${ignore.urls}")
    private List<String> ignoreUrls;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    /**
     * 过期剩余时间，如果过期剩余时间不足60秒，则延长token有效期
     */
    private static final int refreshTime = 60;

    /**
     * 检测header里面是否包含token
     *
     * @param request  请求
     * @param response 返回
     * @return 结果
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader(header);
        return StrUtil.isNotBlank(authorization) && StrUtil.startWith(authorization, tokenType);
    }

    /**
     * 是否允许访问，返回true表示允许，false，请求会被直接拦截
     *
     * @param request     请求
     * @param response    返回
     * @param mappedValue 用户权限
     * @return 结果
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        PathMatcher pathMatcher = new AntPathMatcher();
        String requestPath = ((HttpServletRequest) request).getRequestURI();
        if (CollUtil.isNotEmpty(ignoreUrls)) {
            for (String ignoreUrl : ignoreUrls) {
                if (pathMatcher.match(ignoreUrl, requestPath)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 执行登陆
     *
     * @param request  请求
     * @param response 返回
     * @return 结果
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String authHeader = httpServletRequest.getHeader(header);
        String token = authHeader.substring(tokenType.length());
        JwtToken jwtToken = new JwtToken(token);
        // 提交给realm进行登入，如果错误他会抛出异常并被捕获
        getSubject(request, response).login(jwtToken);

        // 如果过期时间剩余时间不足，则刷新token
        if (DateUtil.between(new Date(), jwtTokenUtil.getExpiration(token), DateUnit.SECOND) < refreshTime) {
            String refreshToken = jwtTokenUtil.refreshToken(token, jwtTokenUtil.getUsernameFromToken(token));
            ((HttpServletResponse) response).setHeader("x-auth-token", refreshToken);
        }
        // 如果没有抛出异常则代表登入成功，返回true
        return true;
    }

    /**
     * onAccessDenied：表示 （isAccessAllowed 返回false时） 访问拒绝时是否自己处理，如果返回true表示自己不处理且继续拦截器链执行，返回false表示自己已经处理了
     *
     * @param request  请求
     * @param response 返回
     * @return 结果
     * @throws Exception 异常
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        try {
            if (isLoginAttempt(request, response)) {
                // 执行登录
                executeLogin(request, response);
                return true;
            } else {
                errorResponse(response, HttpStatus.HTTP_UNAUTHORIZED, "您还未登录，请先登录");
            }
        } catch (Exception e) {
            errorResponse(response, HttpStatus.HTTP_FORBIDDEN, e.getMessage());
        }
        return false;
    }

    /**
     * 返回错误
     *
     * @param response   返回
     * @param httpStatus 网络状态
     * @param message    消息
     * @throws IOException 异常
     */
    private void errorResponse(ServletResponse response, int httpStatus, String message) throws IOException {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setStatus(httpStatus);
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        PrintWriter printWriter = httpServletResponse.getWriter();
        ResponseData responseData = ResponseUtil.error(String.valueOf(httpStatus), message);
        printWriter.write(JSON.toJSONString(responseData));
        printWriter.flush();
    }

}
